﻿// HSS.ServiceModel.VirtualServiceProvider.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       VirtualServiceProvider.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/04/2010
// ----------------------------------------------------------------------------
namespace HSS.ServiceModel
{
	#region Using Directives
	using System;
	using System.Collections;
	using System.IO;
	using System.Security.Permissions;
	using System.Web;
	using System.Web.Caching;
	using System.Web.Hosting;
	using HSS.ServiceModel.Internal;
	using System.Collections.Generic;
	#endregion

	#region VirtualServiceProvider
	/// <summary>
	/// This is the VirtualPathProvider that serves up virtual WCF Service (.svc) files.
	/// The supported uri requests are in the form of "~/Services/Full.Namespace.ClassName.svc"
	/// </summary>
	[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
	[AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.High)]
#if !DEBUG
	[System.Diagnostics.DebuggerStepThroughAttribute()]
#endif
	public class VirtualServiceProvider : VirtualPathProvider
	{
		#region Fields
		static readonly string factoryType = typeof(VirtualServiceHostFactory).FullName;
		static Dictionary<string, VirtualProvider> providers = new Dictionary<string, VirtualProvider>();
		#endregion

		#region Constructors
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="virtualServicesDirectory">The VirtualDirectory that this ServiceProvider is responsible for. Example: ~/services.</param>
		private VirtualServiceProvider(string virtualServicesDirectory)
			: base()
		{
			this.VirtualDirectory = virtualServicesDirectory;
		}
		#endregion

		#region Properties

		/// <summary>
		/// Gets if the VirtualServiceProvider has been initialized.
		/// </summary>
		public static bool IsInitialized
		{
			get;
			private set;
		}
		/// <summary>
		/// Gets if the VirtualServiceProvider requires secured (SSL) connections, when receiving Service requests.
		/// </summary>
		public static bool RequiresSSL
		{
			get;
			private set;
		}
		/// <summary>
		/// Gets the valid Services token.
		/// </summary>
		internal static string ServiceToken
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the VirtualDirectory that the VirtualServiceProvider is responsible for.
		/// </summary>
		private string VirtualDirectory
		{
			get;
			set;
		}

		#endregion

		#region Overrides
		/// <summary>
		/// Verifies if the File Exists
		/// </summary>
		/// <param name="virtualPath">The virtual file path to check</param>
		/// <returns>true if the Virtual File exists; otherwise false</returns>
		public override bool FileExists(string virtualPath)
		{
			string appRelativeVirtualPath = ToAppRelativeVirtualPath(virtualPath);
			if (this.IsVirtualFile(appRelativeVirtualPath))
				return true;
			else
				return this.Previous.FileExists(virtualPath);
		}
		/// <summary>
		/// Gets the requested virtual file
		/// </summary>
		/// <param name="virtualPath">The virtual path to the virtual file</param>
		/// <returns>A VirtualFile (VirtualWCFService)</returns>
		public override VirtualFile GetFile(string virtualPath)
		{
			VirtualFile vfile = null;
			string fileExt = Path.GetExtension(virtualPath);

			if (fileExt == ".svc")
			{
				string appRelativeVirtualPath = this.ToAppRelativeVirtualPath(virtualPath);
				if (this.IsVirtualFile(appRelativeVirtualPath))
				{
					string serviceClass = virtualPath;
					serviceClass = VirtualPathUtility.MakeRelative(VirtualDirectory + "/", serviceClass);
					serviceClass = serviceClass.Substring(0, serviceClass.LastIndexOf(".svc"));
					vfile = new VirtualServiceFile(virtualPath, serviceClass, factoryType);
				}
			}

			if (null == vfile)
				vfile = this.Previous.GetFile(virtualPath);

			return vfile;

		}
		/// <summary>
		/// Get any cache dependencies for the virtual path
		/// </summary>
		/// <param name="virtualPath">The path to look for cached items</param>
		/// <param name="virtualPathDependencies">The list of dependencies</param>
		/// <param name="utcStart">The cache time</param>
		/// <returns>A CacheDependency object based on the virtual path</returns>
		public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
		{
			string appRelativeVirtualPath = ToAppRelativeVirtualPath(virtualPath);
			if (IsVirtualFile(appRelativeVirtualPath) || IsVirtualDirectory(appRelativeVirtualPath))
				return null;
			else
				return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
		}
		#endregion

		#region Methods
		/// <summary>
		/// Initializes the VirtualServiceProvider.
		/// </summary>
		/// <param name="context">A <see cref="VirtualServiceContext"/> object containing the initialization information.</param>
		public static void Initialize(VirtualServiceContext context)
		{
			if (IsInitialized)
				throw new VirtualServiceProviderAlreadyInitializedException();

			IsInitialized = true;
			RequiresSSL = context.RequiresSSL;
			ServiceToken = context.VirtualServiceToken;
		}
		/// <summary>
		/// Initializes the VirtualServiceProvider.
		/// </summary>
		/// <param name="serviceToken">The valid ServiceToken.</param>
		/// <param name="requiresSSL">Indicate if the VirtualServiceProvider requires secured (SSL) connections, when receiving client requests. default: true.</param>
		public static void Initialize(string serviceToken, bool requiresSSL = true)
		{
			if (IsInitialized)
				throw new VirtualServiceProviderAlreadyInitializedException();

			IsInitialized = true;
			RequiresSSL = requiresSSL;
			ServiceToken = serviceToken;
		}
		/// <summary>
		/// Determines if the provided virtual directory exists.
		/// </summary>
		/// <param name="virtualDirectory">The virtual directory to evaluate.</param>
		/// <returns>true if it exists; otherwise false.</returns>
		public static bool IsRegistered(string virtualDirectory)
		{
			return providers.ContainsKey(virtualDirectory.ToLowerInvariant());
		}
		/// <summary>
		/// Registers a new VirtualServiceProvider instance with the HostingEnvironment for the provided VirtualPath.
		/// </summary>
		/// <param name="virtualDirectory">The VirtualDirectory that this ServiceProvider is responsible for. Example: ~/services.</param>
		/// <param name="keepAliveEnabled">Indicate if HTTP KeepAlive should be disabled or enabled. Default: false.</param>
		/// <exception cref="HSS.ServiceModel.VirtualServicesNotInitializedException">Cannot register a virtual directory until the VirtualServiceProvider has been initialized.</exception>
		/// <exception cref="System.ArgumentOutOfRangeException">Duplicate virtual directory not allowed.</exception>
		/// <exception cref="System.ArgumentNullException">The provided virtual directory cannot be null.</exception>
		/// <exception cref="System.ArgumentException">The provided virtual directory must not be empty.</exception>
		public static void Register(string virtualDirectory, bool keepAliveEnabled = true)
		{
			if (!IsInitialized)
				throw new VirtualServicesNotInitializedException();

			virtualDirectory = VirtualUtilities.FormatVDir(virtualDirectory);

			if (providers.ContainsKey(virtualDirectory))
				throw new ArgumentOutOfRangeException("Duplicate virtual directory not allowed.");

			var p = new VirtualProvider { VirtualDirectory = virtualDirectory, KeepAliveEnabled = keepAliveEnabled };
			providers.Add(virtualDirectory, p);

			var provider = new VirtualServiceProvider(virtualDirectory);
			HostingEnvironment.RegisterVirtualPathProvider(provider);
		}
		/// <summary>
		/// Gets if the provided VirtualDirectory Services use KeepAlive.
		/// </summary>
		/// <param name="virtualDirectory">The VirtualDirectory to evaluate.</param>
		/// <returns>true if the VirtualDirectory uses KeepAlive.</returns>
		internal static bool KeepAliveEnabled(string virtualDirectory)
		{
			virtualDirectory = VirtualUtilities.FormatVDir(virtualDirectory);
			VirtualProvider p;
			if (providers.TryGetValue(virtualDirectory, out p))
				return p.KeepAliveEnabled;
			return false;
		}
		private bool IsVirtualFile(string appRelativeVirtualPath)
		{
			if (appRelativeVirtualPath.StartsWith(VirtualDirectory + "/", StringComparison.OrdinalIgnoreCase))
				return true;
			return false;
		}
		private bool IsVirtualDirectory(string appRelativeVirtualPath)
		{
			return appRelativeVirtualPath.Equals(VirtualDirectory + "/", StringComparison.OrdinalIgnoreCase);
		}
		private string ToAppRelativeVirtualPath(string virtualPath)
		{
			string appRelativeVirtualPath = VirtualPathUtility.ToAppRelative(virtualPath);
			if (!appRelativeVirtualPath.StartsWith("~/"))
				throw new HttpException("Unexpectedly does not start with ~.");
			return appRelativeVirtualPath;
		}
		#endregion

		#region Nested
		class VirtualProvider
		{
			public VirtualProvider()
			{

			}

			public string VirtualDirectory { get; set; }
			public bool KeepAliveEnabled { get; set; }
		}
		#endregion
	}
	#endregion
}